package net.yihang.aspect;

import com.google.common.cache.Cache;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;

@Aspect
public class CacheAspect {

    private Cache<String,Object> cache;

    public CacheAspect(Cache<String, Object> cache) {
        this.cache = cache;
    }

    // 只有方法加了Cacheable，才调用通知
    @Around("@annotation(net.yihang.aspect.Cacheable)")
    public Object checkCache(ProceedingJoinPoint pjp) throws Throwable {
        // 获取方法签名对象
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        // 获取方法对象
        Method method = signature.getMethod();
        // 计算key
        String key = computeKey(pjp, method);
        // 查看缓存
        Object value = cache.getIfPresent(key);
        // 缓存没查到，调用目标方法，查询数据库
        if(value == null) {
            value = pjp.proceed();
            // 数据库查询结果为null，不缓存
            if(value != null) {
                cache.put(key, value);
            }
        }
        return value;
    }

    private String computeKey(ProceedingJoinPoint pjp, Method method) {
        // 获取方法上@Cacheable注解
        Cacheable cacheable = method.getAnnotation(Cacheable.class);
        /*
            拿到注解的keyPrefix属性，但光凭它不能确定真正key的唯一性：
            比如 findById(1)  findById(2) 调用时id值是不一样的
            所以还需要根据方法的实际参数运算得到最终的key
         */
        StringBuilder keyBuilder = new StringBuilder(32);

        // 得到key前缀
        keyBuilder.append(cacheable.keyPrefix());

        // 得到方法参数名称，注意在编译时添加 -parameters选项保留方法参数名
        String[] names = cacheable.argumentNames();
        Arrays.sort(names);

        // 看看哪些参数需要拼接到key当中
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            if(Arrays.binarySearch(names, parameter.getName())>=0){
                // 拼接实际参数值到key
                keyBuilder.append("_").append(pjp.getArgs()[i]);
            }
        }
        System.out.println("这次的key是:"+keyBuilder.toString());
        return keyBuilder.toString();
    }
}
